using System;
using System.Collections.Generic;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * Objects that represent elements in the compiled 
     * tree representation of the template necessarily 
     * descend from this abstract class.
     */

    public abstract class TemplateElement : TemplateObject //, TreeNode
    {
        internal TemplateElement Parent;

        // Only one of nestedBlock and nestedElements can be non-null.

        internal TemplateElement NestedBlock;

        protected List<TemplateElement> NestedElements;

        /**
         * Processes the contents of this <tt>TemplateElement</tt> and
         * outputs the resulting text
         *
         * @param env The runtime environment
         */
        internal abstract void Accept(Environment env);

        public abstract string GetDescription();

        // Methods to implement TemplateNodeModel 

        public ITemplateNodeModel GetParentNode()
        {
            // return parent;
            return null;
        }

        public string GetNodeNamespace()
        {
            return null;
        }

        public string GetNodeType()
        {
            return "element";
        }

        public ITemplateSequenceModel GetChildNodes()
        {
            if (NestedElements != null)
            {
                return new SimpleSequence(NestedElements);
            }
            var result = new SimpleSequence();
            if (NestedBlock != null)
            {
                result.Add(NestedBlock);
            }
            return result;
        }

        public string GetNodeName()
        {
            string classname = GetType().FullName;
            int shortNameOffset = classname.LastIndexOf('.') + 1;
            return classname.Substring(shortNameOffset);
        }

        // Methods so that we can implement the Swing TreeNode API.    

        public bool IsLeaf()
        {
            return NestedBlock == null
                   && (NestedElements == null || NestedElements.Count == 0);
        }

        public bool GetAllowsChildren()
        {
            return !IsLeaf();
        }

        // TODO: public int GetIndex(TreeNode node)
        //{
        //    if (NestedBlock is MixedContent)
        //    {
        //        return NestedBlock.getIndex(node);
        //    }
        //    if (NestedBlock != null)
        //    {
        //        if (node == NestedBlock)
        //        {
        //            return 0;
        //        }
        //    }
        //    else if (NestedElements != null)
        //    {
        //        return NestedElements.IndexOf(node);
        //    }
        //    return -1;
        //}

        public int GetChildCount()
        {
            if (NestedBlock is MixedContent)
            {
                return NestedBlock.GetChildCount();
            }
            if (NestedBlock != null)
            {
                return 1;
            }
            if (NestedElements != null)
            {
                return NestedElements.Count;
            }
            return 0;
        }

        //public Enumeration Children()
        //{
        //    if (NestedBlock is MixedContent)
        //    {
        //        return NestedBlock.Children();
        //    }
        //    if (NestedBlock != null)
        //    {
        //        return Collections.enumeration(Collections12.singletonList(NestedBlock));
        //    }
        //    if (NestedElements != null)
        //    {
        //        return Collections.enumeration(NestedElements);
        //    }
        //    return Collections.enumeration(Collections.EMPTY_LIST);
        //}

        //public TreeNode GetChildAt(int index)
        //{
        //    if (NestedBlock is MixedContent)
        //    {
        //        return NestedBlock.getChildAt(index);
        //    }
        //    if (NestedBlock != null)
        //    {
        //        if (index == 0)
        //        {
        //            return NestedBlock;
        //        }
        //        throw new IndexOutOfRangeException("invalid index");
        //    }
        //    if (NestedElements != null)
        //    {
        //        return (TreeNode) NestedElements.get(index);
        //    }
        //    throw new IndexOutOfRangeException("element has no children");
        //}

        public void SetChildAt(int index, TemplateElement element)
        {
            if (NestedBlock is MixedContent)
            {
                NestedBlock.SetChildAt(index, element);
            }
            else if (NestedBlock != null)
            {
                if (index == 0)
                {
                    NestedBlock = element;
                    element.Parent = this;
                }
                else
                {
                    throw new IndexOutOfRangeException("invalid index");
                }
            }
            else if (NestedElements != null)
            {
                NestedElements.Insert(index, element);
                element.Parent = this;
            }
            else
            {
                throw new IndexOutOfRangeException("element has no children");
            }
        }

        //public TreeNode GetParent()
        //{
        //    return Parent;
        //}

        // Walk the tree and set the parent field in all the nested elements recursively.

        internal void SetParentRecursively(TemplateElement parent)
        {
            Parent = parent;
            int nestedSize = NestedElements == null ? 0 : NestedElements.Count;
            for (int i = 0; i < nestedSize; i++)
            {
                NestedElements[i].SetParentRecursively(this);
            }
            if (NestedBlock != null)
            {
                NestedBlock.SetParentRecursively(this);
            }
        }

        /**
         * We walk the tree and do some cleanup 
         * @param stripWhitespace whether to clean up superfluous whitespace
         */
        internal virtual TemplateElement PostParseCleanup(bool stripWhitespace)
        {
            if (NestedElements != null)
            {
                for (int i = 0; i < NestedElements.Count; i++)
                {
                    TemplateElement te = NestedElements[i];
                    te = te.PostParseCleanup(stripWhitespace);
                    NestedElements[i] = te;
                    te.Parent = this;
                }
                if (stripWhitespace)
                {
                    for (int i = NestedElements.Count - 1; i >= 0; i--)
                    {
                        TemplateElement te = NestedElements[i];
                        if (te.IsIgnorable())
                        {
                            NestedElements.RemoveAt(i);
                        }
                    }
                }
                NestedElements.TrimExcess();
            }
            if (NestedBlock != null)
            {
                NestedBlock = NestedBlock.PostParseCleanup(stripWhitespace);
                if (NestedBlock.IsIgnorable())
                {
                    NestedBlock = null;
                }
                else
                {
                    NestedBlock.Parent = this;
                }
            }
            return this;
        }

        internal virtual bool IsIgnorable()
        {
            return false;
        }

        // The following methods exist to support some fancier tree-walking 
        // and were introduced to support the whitespace cleanup feature in 2.2

        internal TemplateElement PrevTerminalNode()
        {
            TemplateElement prev = PreviousSibling();
            if (prev != null)
            {
                return prev.GetLastLeaf();
            }
            if (Parent != null)
            {
                return Parent.PrevTerminalNode();
            }
            return null;
        }

        internal TemplateElement NextTerminalNode()
        {
            TemplateElement next = NextSibling();
            if (next != null)
            {
                return next.GetFirstLeaf();
            }
            if (Parent != null)
            {
                return Parent.NextTerminalNode();
            }
            return null;
        }

        internal TemplateElement PreviousSibling()
        {
            if (Parent == null)
            {
                return null;
            }
            List<TemplateElement> siblings = Parent.NestedElements;
            if (siblings == null)
            {
                return null;
            }
            for (int i = siblings.Count - 1; i >= 0; i--)
            {
                if (siblings[i] == this)
                {
                    return (i > 0) ? siblings[i - 1] : null;
                }
            }
            return null;
        }

        internal TemplateElement NextSibling()
        {
            if (Parent == null)
            {
                return null;
            }
            List<TemplateElement> siblings = Parent.NestedElements;
            if (siblings == null)
            {
                return null;
            }
            for (int i = 0; i < siblings.Count; i++)
            {
                if (siblings[i] == this)
                {
                    return (i + 1) < siblings.Count ? siblings[i + 1] : null;
                }
            }
            return null;
        }

        private TemplateElement GetFirstChild()
        {
            if (NestedBlock != null)
            {
                return NestedBlock;
            }
            if (NestedElements != null && NestedElements.Count > 0)
            {
                return NestedElements[0];
            }
            return null;
        }

        private TemplateElement GetLastChild()
        {
            if (NestedBlock != null)
            {
                return NestedBlock;
            }
            if (NestedElements != null && NestedElements.Count > 0)
            {
                return NestedElements[NestedElements.Count - 1];
            }
            return null;
        }

        private TemplateElement GetFirstLeaf()
        {
            TemplateElement te = this;
            while (!te.IsLeaf() && !(te is Macro) && !(te is BlockAssignment))
            {
                // A macro or macro invocation is treated as a leaf here for special reasons
                te = te.GetFirstChild();
            }
            return te;
        }

        private TemplateElement GetLastLeaf()
        {
            TemplateElement te = this;
            while (!te.IsLeaf() && !(te is Macro) && !(te is BlockAssignment))
            {
                // A macro or macro invocation is treated as a leaf here for special reasons
                te = te.GetLastChild();
            }
            return te;
        }

        /**
         * determines whether this element's presence on a line 
         * indicates that we should not strip opening whitespace
         * in the post-parse whitespace gobbling step.
         */

        internal virtual bool HeedsOpeningWhitespace()
        {
            return false;
        }

        /**
         * determines whether this element's presence on a line 
         * indicates that we should not strip trailing whitespace
         * in the post-parse whitespace gobbling step.
         */

        internal virtual bool HeedsTrailingWhitespace()
        {
            return false;
        }
    }
}